/**
 * Copyright (C) 2021 - 2031 O-Cubes Co., Ltd.
 */

/****************************************************************
 *  @file    dw_rtc.c
 *  @brief   Designware rtc driver
 *  @version v1.0
 *  @date    03. Apr. 2023
 *  @author  liuchao
 ****************************************************************/

#include "io.h"
#include "common.h"
#include "error.h"
#include "dw_rtc.h"
#include "mem_map_table.h"
#include "crg.h"
#include "irq.h"

#define RTC_BASE_ADDR              MEM_MAP_RTC_BASE_ADDR
#define RTC_CMR_DELTA_S            (0xFFFFFF)
#define RTC_OFF(member)            (GET_OFFSET_OF_ADDR(RTC_BASE_ADDR, struct dw_rtc, member))
#define LEAPS_THRU_END_OF(y)       ((y)/4 - (y)/100 + (y)/400)

static DEV_CALLBACK rtc_callback;

static const uint8_t rtc_days_in_month[] = {
	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

static const uint16_t rtc_ydays[2][13] = {
	/* normal years */
	{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
	/* leap years */
	{ 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
};

static inline int32_t is_leap_year(uint32_t year)
{
	return (!(year % 4) && (year % 100)) || !(year % 400);
}

/*
 * the number of days in the month.
 */
static int32_t rtc_month_days(uint32_t month, uint32_t year)
{
	return rtc_days_in_month[month] + (is_leap_year(year) && month == 1);
}

/*
 * the number of days since January 1. (0 to 365)
 */

static int32_t rtc_year_days(uint32_t day, uint32_t month, uint32_t year)
{
	return rtc_ydays[is_leap_year(year)][month] + day - 1;
}

static inline uint32_t div_rem(uint32_t dividend, uint32_t divisor, uint32_t *remainder)
{
	*remainder = dividend % divisor;
	return dividend / divisor;
}

static void rtc_time_to_tm(uint32_t time, Datetime_t_Ptr tm)
{
	uint32_t month, year, secs;
	int32_t days;

	/* time must be positive */
	days = div_rem(time, 86400, &secs);

	/* day of the week, 1970-01-01 was a Thursday */
	tm->tm_wday = (days + 4) % 7;

	year = 1970 + days / 365;
	days -= (year - 1970) * 365 + LEAPS_THRU_END_OF(year - 1) - LEAPS_THRU_END_OF(1970 - 1);

	while (days < 0) {
		year -= 1;
		days += 365 + is_leap_year(year);
	}

	tm->tm_year = year;//tm->tm_year = year - 1900;
	tm->tm_yday = days + 1;

	for (month = 0; month < 11; month++) {
		int newdays;

		newdays = days - rtc_month_days(month, year);

		if (newdays < 0)
			break;

		days = newdays;
	}

	tm->tm_mon = month;
	tm->tm_mday = days + 1;

	tm->tm_hour = secs / 3600;
	secs -= tm->tm_hour * 3600;
	tm->tm_min = secs / 60;
	tm->tm_sec = secs - tm->tm_min * 60;

	tm->tm_isdst = 0;
}

uint32_t mktime(const uint32_t year0, const uint32_t mon0, const uint32_t day,
                const uint32_t hour, const uint32_t min, const uint32_t sec)
{
	uint32_t mon = mon0, year = year0;

	/* 1..12 -> 11,12,1..10 */
	if (0 >= (int32_t) (mon -= 2)) {
		mon += 12;	/* Puts Feb last since it has leap day */
		year -= 1;
	}

	return ((((year / 4 - year / 100 + year / 400 + 367 * mon / 12 + day) + year * 365 - 719499) * 24 +
		hour) * 60 + min) * 60 + sec;
}

uint32_t rtc_tm_to_time(Datetime_t_Ptr tm)
{
	return mktime(tm->tm_year, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
}

/*
 * does the datetime represent a valid date/time ?
 */
int32_t rtc_valid_tm(Datetime_t_Ptr tm)
{
	if (tm->tm_year < 70 || tm->tm_mon >= 12 || tm->tm_mday < 1
		|| tm->tm_mday > rtc_month_days(tm->tm_mon, tm->tm_year + 1900)
		|| tm->tm_hour >= 24 || tm->tm_min >= 60 || tm->tm_sec >= 60)
		return E_FAILURE;

	return E_OK;
}

static inline uint32_t rtc_get_count(void)
{
	uint32_t reg = RTC_OFF(RTC_CCVR);

	return readl(reg);
}

static inline void rtc_set_count(u32 count)
{
	uint32_t reg = RTC_OFF(RTC_CLR);

	writel(count, reg);
}

static inline void rtc_clr_int(void)
{
	uint32_t reg = RTC_OFF(RTC_EOI);

	readl(reg);
}

//void rtc_init(void)
//{
//	rtc_stop();
//	rtc_set_count(0);
//	rtc_clr_int();
//}

void rtc_start(void)
{
	uint32_t reg;
	uint32_t val = 0;

	reg = RTC_OFF(RTC_CMR);
	writel(RTC_CMR_DELTA_S, reg);
	
	reg = RTC_OFF(RTC_CPSR);
	val = get_mod_clk_rate(CRG_MOD_RTC);
	writel(val, reg);

	reg = RTC_OFF(RTC_CCR);
	val &= ~ RTC_IEN;
	val |= RTC_EN;
	val |= RTC_PSCLR_EN;
	val &= ~ RTC_MASK;
	writel(val, reg);

	irq_attach(RTC_IRQn, rtc_isr, NULL);
}

void rtc_stop(void)
{
	uint32_t val = 0;
	uint32_t reg = RTC_OFF(RTC_CCR);

	val |= RTC_MASK;
	val &= ~ RTC_IEN;
	val &= ~ RTC_EN;
	val &= ~ RTC_PSCLR_EN;
	writel(val, reg);
}

void rtc_init(void)
{
	//rtc_stop();
	//rtc_set_count(0);
	rtc_clr_int();
	rtc_start();
}

void rtc_isr(void *args)
{
	uint32_t reg = RTC_OFF(RTC_STAT);
	uint32_t status;
	uint32_t count;
	Datetime_t tm;

	(void)(args);

	status = readl(reg);

	/* check if interrupt asserted */
	if (!status & RTC_INT_ACTIVE)
		return;

	/* clear interrupt */
	rtc_clr_int();

	/* next interrupt */
	count = rtc_get_count() + RTC_CMR_DELTA_S;
	reg = RTC_OFF(RTC_CMR);
	writel(count, reg);

	rtc_get_time(&tm);

	if (rtc_callback) {
		rtc_callback(CONV2VOID(&tm));
	}
}

int32_t rtc_get_time(Datetime_t_Ptr tm)
{
	uint32_t rtc_count = rtc_get_count();

	rtc_time_to_tm(rtc_count, tm);

	return 0;
}

void rtc_set_time(Datetime_t_Ptr tm)
{
	uint32_t rtc_count;

	rtc_count = rtc_tm_to_time(tm);

	rtc_set_count(rtc_count);
}

int32_t rtc_set_alarm(uint32_t seconds)
{
	uint32_t alarm_time;
	Datetime_t tm;
	uint32_t reg;
	uint32_t ccr;

	rtc_get_time(&tm);
	alarm_time = rtc_tm_to_time(&tm) + seconds;
	reg = RTC_OFF(RTC_CMR);
	writel(alarm_time, reg);

	reg = RTC_OFF(RTC_CCR);
	ccr = readl(reg);
	ccr &= ~RTC_MASK;
	ccr |= RTC_IEN;
	writel(ccr, reg);

	return 0;
}

void rtc_register_callback(DEV_CALLBACK callback)
{
	rtc_callback = callback;
}

